// generating config files for a logical node
// Created: Apr. 13, 2023 by SUN Yazhou, asia.rabbit@163.com

#include <iostream>
#include <algorithm>
#include "LN.h"
#include "Port.h"

using std::endl;
using std::cerr;
using std::swap;

const map<CMD_TYPE, const char *> LN::kCmd_string = {
  {DATA_SAVE, "data_save"}, {SYS_CONSOLE_GUI, "sys_console_gui"},
  {REDUCE_INDEX, "reduce_index"}, {MAP_INDEX, "map_index"}, {RECEIVE, "receive"},
  {TRANSMIT, "transmit"}, {DSI, "dsi"}, {MASTER_TRIGGER, "master_trigger"},
  {SLAVE_TRIGGER, "slave_trigger"}
};

const map<STREAM_TYPE, const char *> LN::kStream_string = {
  {DATA, "data"}, {CMD, "cmd"}, {STA, "status"}, {STREAM_OTHER, "other"}
};

LN::LN(int lnid, CMD_TYPE cmd, CRATE_TYPE ct, STREAM_TYPE stream, const string &opt)
  : fLnId(lnid), fCmd(cmd), fStreamType(stream), fCrateType(ct), fOption(opt){
  fPortVec.clear();
} // end ctor

LN::~LN(){
  for(auto &p : fPortVec) if(p) delete p;
} // end LN

ofstream &operator<<(ofstream &fout, LN &ln){
  ln.Check(); // check the validity of the Ln first

  fout << "## " << LN::kStream_string.at(ln.fStreamType) << " stream ##" << endl;
  fout << "[Stream0Ln" << ln.fLnId << "]" << endl;
  fout << "id=" << ln.fLnId << "\nCmdName=" << LN::kCmd_string.at(ln.fCmd) << endl;
  fout << "PortNum=" << ln.fPortVec.size() << endl;
  // if("" != ln.fOption) fout << "option=" << ln.fOption << endl;
  fout << "option=" << ln.fOption << endl;
  fout << endl;
  for(auto &p : ln.fPortVec) fout << *p << endl;

  return fout;
} // end function operator<<

void LN::AddPort(Port *p){
  if(!p){
    cerr << "LN:AddPort: NULL pointer" << endl;
    exit(-1);
  } // end if
  fPortVec.push_back(p);
} // end member function AddPort

// this -> ln
LN &LN::operator>>(LN &ln){
  Port *p0 = nullptr, *p1 = nullptr; // pin, pout
  // add port to this //
  bool isIn = false, isShm = (TRANSMIT != fCmd);
  AddPort(p0 = new Port(isIn, isShm, fLnId, fPortVec.size()));
  p0->SetStreamType(StreamType() != STREAM_OTHER ? StreamType() : ln.StreamType());
  if(isShm) p0->SetLnId1(ln.fLnId);
  else p0->SetTsmIPPort(ln.IP(), IP(), CrateType() == CRATE_CMC);

  // add port to ln //
  isIn = true; isShm = (RECEIVE != ln.fCmd);
  ln.AddPort(p1 = new Port(isIn, isShm, ln.fLnId, ln.fPortVec.size()));
  p1->SetStreamType(StreamType() != STREAM_OTHER ? StreamType() : ln.StreamType());
  if(isShm) p1->SetLnId1(fLnId);
  else p1->SetRcvIPPort(p0->IPPort());

  return ln;
} // end member function operator>>

// make sure the ln port is legal
void LN::Check(){
  auto &v = fPortVec;
  int n = v.size();
  switch(fCmd){
    case REDUCE_INDEX: // put the output port to the end
      for(int i = 0; i < n-1; i++) if(!v[i]->IsIn()) swap(v[i], v[n-1]);
      break;
    // put the input port to the front
    case MAP_INDEX:
    case SLAVE_TRIGGER:
    case MASTER_TRIGGER:
      for(int i = 1; i < n; i++) if(v[i]->IsIn()) swap(v[i], v[0]);
      break;
    case RECEIVE: // [0]: socket; [1]: shm
      if(v[0]->IsShm()) swap(v[0], v[1]);
      break;
    case TRANSMIT: // [0]: shm; [1]: socket
      if(v[1]->IsShm()) swap(v[0], v[1]);
      break;
    case SYS_CONSOLE_GUI: // [0]: in; [1]: out
      if(v[1]->IsIn()) swap(v[0], v[1]);
      break;
    case DSI: // [0]: data; [1]: cmd; [2]: status
      for(int i = 0; i < 3; i++) // 1. put cmd in the middle
        if(i != 1 && v[i]->IsIn()) swap(v[i], v[1]);
      if(v[0]->StreamType() != DATA) swap(v[0], v[2]); // 2. push data to the front
    default: break;
  } // end switch
  // update port id //
  int id = 0;
  for(auto &p : v) p->SetPortId(id++);
} // end member function check
